﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.CodeDom.Compiler;
using Microsoft.CSharp;

namespace SmartMathLibrary.ExpressionEvaluation
{
    /// <summary>
    /// The MathExpressionParser is a parser for mathematic formulas. The following operations
    /// are supported: Abs, Acos, Asin, Atan, Atan2, Ceiling, Cos, DivRem, Exp, Floor, Log, 
    /// Log10, Pow, Round, Sin, Tan, Truncate.                                      
    /// </summary>
    [Serializable]
    public class ExpressionEvaluator : AbstractExpressionEvaluator
    {
        /// <summary>
        /// The expression to parse.
        /// </summary>
        private String expression;

        /// <summary>
        /// Initializes a new instance of the <see cref="ExpressionEvaluator"/> class.
        /// </summary>
        public ExpressionEvaluator()
        {
            this.expression = "";
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ExpressionEvaluator"/> class.
        /// </summary>
        /// <param name="expression">The expression to parse.</param>
        public ExpressionEvaluator(String expression)
        {
            if (expression == (String) null)
            {
                throw new ArgumentNullException("expression");
            }

            this.expression = expression;
        }

        /// <summary>
        /// Gets or sets the expression for the parser.
        /// </summary>
        /// <value>The expression for the parser.</value>
        public String Expression
        {
            get { return expression; }
            set { expression = value; }
        }

        /// <summary>
        /// Calculates the result of the mathematical expression.
        /// </summary>
        /// <returns>The result of the mathematical expression.</returns>
        public override double Evaluate()
        {
            try
            {
                object parser = GenerateParser(PrepareExpression(this.expression));
                string result = parser.GetType().GetMethod("Eval").Invoke(parser, null).ToString();

                return Double.Parse(result, NumberStyles.Float, NumberFormatInfo.InvariantInfo);
            }
            catch (Exception e)
            {
                throw new ParserException(e.ToString());
            }
        }

        /// <summary>
        /// Prepares the expression for the parser.
        /// </summary>
        /// <param name="result">The expression which sould be prepared.</param>
        /// <returns>The prepared string for the parser.</returns>
        private static String PrepareExpression(String result)
        {
            Regex reg = new Regex("[/+]|[/-]|[/*]|[//]");
            List<String> destination = new List<string>();

            while (reg.IsMatch(result))
            {
                double tmp = 0;
                Match tempuri = reg.Match(result);

                if (Double.TryParse(result.Substring(0, tempuri.Index).ToString(), out tmp))
                {
                    destination.Add(result.Substring(0, tempuri.Index));
                }
                else
                {
                    destination.Add("Math." + UpperCaseFirstLetter(result.Substring(0, tempuri.Index)));
                }

                destination.Add(tempuri.Value);

                result = result.Remove(0, tempuri.Index + 1);
            }

            if (result.Length > 0)
            {
                destination.Add(result);
            }

            String newResult = "";

            for (int i = 0; i < destination.Count; i++)
            {
                newResult += destination[i] + " ";
            }

            return newResult.Trim();
        }

        /// <summary>
        /// Uppers the first letter of a string. The rest of the string will converted
        /// to lower case.
        /// </summary>
        /// <param name="value">The value to convert.</param>
        /// <returns>The converted string.</returns>
        private static String UpperCaseFirstLetter(String value)
        {
            String result = value[0].ToString().ToUpper();

            return result + value.Remove(0, 1).ToLower();
        }

        /// <summary>
        /// Generates the parser.
        /// </summary>
        /// <param name="mathExpression">The expression to parse.</param>
        /// <returns>The object, which includes the assembly for the parser.</returns>
        private static Object GenerateParser(string mathExpression)
        {
            StringBuilder source = new StringBuilder();

            source.Append("using System;");
            source.Append("using System.Collections;");
            source.Append("public class DynamicMathExpressionParser");
            source.Append("{");
            source.Append("  public double Eval()");
            source.Append("  {");
            source.Append("          return " + mathExpression + ";");
            source.Append("  }");
            source.Append("}");

            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters parameters = new CompilerParameters();
            CompilerResults result = provider.CompileAssemblyFromSource(parameters, source.ToString());

            if (result.Errors.Count > 0)
            {
                throw new ParserException("The specified expression cannot be parsed.");
            }

            return result.CompiledAssembly.CreateInstance("DynamicMathExpressionParser");
        }
    }
}